package com.dsteiner.widgets.server;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.dsteiner.widgets.client.domain.Distinct;
import com.dsteiner.widgets.client.domain.Distinctable;
import com.dsteiner.widgets.client.domain.DistinctableProperty;
import com.dsteiner.widgets.client.domain.ModelObject;
import com.dsteiner.widgets.client.event.DataUpdateEvent;
import com.dsteiner.widgets.client.event.EventListener;
import com.dsteiner.widgets.client.event.EventManagerImpl;
import com.dsteiner.widgets.client.rpc.DistinctRequest;
import com.dsteiner.widgets.client.rpc.DistinctService;
import com.dsteiner.widgets.client.rpc.SearchResult;
import com.dsteiner.widgets.server.dao.BaseDataDao;
import com.dsteiner.widgets.server.dao.Parameter;
import com.dsteiner.widgets.server.dao.jdo.BaseDataDaoJdo;
import com.dsteiner.widgets.server.util.CacheUtil;
import com.dsteiner.widgets.server.util.CacheUtil.CacheUtilMap;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class DistinctServiceImpl extends RemoteServiceServlet implements DistinctService{

	private static final String CACHE_NAME_DISTINCTS = "distincts";

	public static Object getDistinctValue(String property, ModelObject object) {
		Object propertyValue = ServerUtil.getProperty(property, object);
		if(propertyValue instanceof String){
			propertyValue = ((String)propertyValue).replaceAll("(\\s|\\().*", "");
		}
		return propertyValue;
	}

	private BaseDataDao dataDao = new BaseDataDaoJdo();
	private CacheUtil cacheUtil = CacheUtilMap.getInstance();
	
	public DistinctServiceImpl() {
		setEventManager(EventManagerImpl.getInstance());
	}
	
	private void setEventManager(EventManagerImpl eventManager) {
		eventManager.registerListener(DataUpdateEvent.class, new EventListener<DataUpdateEvent>() {
			public void onEvent(DataUpdateEvent event) {
				if(!(event.getObject() instanceof Distinct))
					return;
				cacheUtil.removeCacheValue(CACHE_NAME_DISTINCTS, getDistinctCachePattern((Distinct) event.getObject()));
			}
		});
	}

	public void removeDistinct(Distinct distinct){
		List<Parameter> parametersList = new ArrayList<Parameter>();
		parametersList.add(new Parameter("className", distinct.getClassName()));
		parametersList.add(new Parameter("property", distinct.getProperty()));
		parametersList.add(new Parameter("value", distinct.getValue()));
		List<Distinct> distincts = dataDao.getObjects(Distinct.class, parametersList);
		if(distincts != null && !distincts.isEmpty()){
			Distinct distinctToUpdate = distincts.get(0);
			distinctToUpdate.decreaseCount();
			if(distinctToUpdate.getCount() <= 0){
				dataDao.deleteObject(distinctToUpdate);
			}
			else{
				dataDao.updateObject(distinctToUpdate);
			}
			cacheUtil.removeCacheValue(CACHE_NAME_DISTINCTS, getDistinctCachePattern(distinctToUpdate));
		}
	}
	
	private Distinct getDistinctCachePattern(Distinct distinct) {
		return new Distinct(distinct.getClassName(), distinct.getProperty());
	}

	public void addDistinct(Distinct distinct){
		List<Parameter> parametersList = new ArrayList<Parameter>();
		parametersList.add(new Parameter("className", distinct.getClassName()));
		parametersList.add(new Parameter("property", distinct.getProperty()));
		parametersList.add(new Parameter("value", distinct.getValue()));
		List<Distinct> distincts = dataDao.getObjects(Distinct.class, parametersList);
		if(distincts != null && !distincts.isEmpty()){
			Distinct distinctToUpdate = distincts.get(0);
			distinctToUpdate.increaseCount();
			dataDao.updateObject(distinctToUpdate);
		}
		else
			dataDao.save(distinct);
		cacheUtil.removeCacheValue(CACHE_NAME_DISTINCTS, getDistinctCachePattern(distinct));
	}

	public Collection<Distinct> createDistincts(String className, String property){
		List<Distinct> savedDistincts = new ArrayList<Distinct>();
		try {
			List<Parameter> parametersList = new ArrayList<Parameter>();
			parametersList.add(new Parameter("className", className));
			parametersList.add(new Parameter("property", property));
			dataDao.deleteObjects(Distinct.class, parametersList);

			Map<Object, Distinct> distincts = new HashMap<Object, Distinct>();
			Class modelObjectClass = Class.forName(className);
			List<? extends ModelObject> modelObjects = dataDao.getObjects(modelObjectClass);
			for (ModelObject object : modelObjects) {
				Object propertyValue = getDistinctValue(property, object);
				Distinct distinct = distincts.get(propertyValue);
				if(distinct == null){
					distinct = new Distinct();
					distinct.setClassName(className);
					distinct.setProperty(property);
					distinct.setValue(propertyValue);
					distincts.put(propertyValue, distinct);
				}
				else{
					distinct.increaseCount();
				}
			}
			for (Distinct distinct : distincts.values()) {
				savedDistincts.add(distinct);
			}
			Collection<Distinct> results = dataDao.save(savedDistincts);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return savedDistincts;
	}

	public List<Distinct> createDistinctsFromAnnotations(ModelObject savedObj){
		Class<? extends ModelObject> savedObjectClass = savedObj.getClass();
		String savedObjectClassName = savedObjectClass.getName();
		boolean annotationPresent = savedObjectClass.isAnnotationPresent(Distinctable.class);
		List<Distinct> distinctables = new ArrayList<Distinct>();
		if(annotationPresent){
			Set<Field> annotationFields = ServerUtil.getAnnotationFields(savedObjectClass, DistinctableProperty.class);
			for (Field field : annotationFields) {
				Object propertyValue = getDistinctValue(field.getName(), savedObj);
				distinctables.add(new Distinct(savedObjectClass.getName(), field.getName(), propertyValue));
			}
		}
		return distinctables;
	}

	public List<Distinct> getDistincts(Distinct distinct, int limit){
		List<Parameter> parametersList = new ArrayList<Parameter>();
		parametersList.add(new Parameter("className", distinct.getClassName()));
		parametersList.add(new Parameter("property", distinct.getProperty()));
		List<Distinct> fields = (List<Distinct>) cacheUtil.getCacheValue(CACHE_NAME_DISTINCTS, distinct);
		if(fields == null){
			fields = dataDao.getObjects(Distinct.class, parametersList, "count desc", limit);
			cacheUtil.setCacheValue(CACHE_NAME_DISTINCTS, distinct, fields);
		}
		return fields;
	}
	
	
	public List<Distinct> getDistincts(DistinctRequest distinctRequest) {
		Distinct[] distincts = distinctRequest.getDistincts();
		int limit = distinctRequest.getLimit();
		List<Distinct> distinctsResult = new ArrayList<Distinct>();
		for (Distinct iterator : distincts) {
			distinctsResult.addAll(getDistincts(iterator, limit));
		}
		return distinctsResult;
	}


	public void setDataDao(BaseDataDao dataDao) {
		this.dataDao = dataDao;
	}

}
